#include "pathlib.h"
#include <shlobj.h>
#include <stdlib.h>
#include <stdio.h>

//////////////////////////////////////////////////////////////////////////

void StripTrailingDir(char *szPath);
void GetFullPathName(const char *szIn, char *szOut);
void ExpandFilenameWildcard(const char *szSource, const char *szDest, char *szExpandedDest);
bool IsDifferentVolumes(const char *szPath1, const char *szPath2);
void ExpandFilenameWildcardPart(const char *szSource, const char *szDest, char *szExpandedDest);

//////////////////////////////////////////////////////////////////////////

namespace pathlib
{

bool DoesFileExist(const char *szFilename)
{
	if ( strchr(szFilename,'*')||strchr(szFilename,'?') )
	{
		WIN32_FIND_DATA	wfd;

		HANDLE hFile = FindFirstFile(szFilename, &wfd);

		if ( hFile == INVALID_HANDLE_VALUE )
			return false;

		FindClose(hFile);
		return true;
	}
	else
	{
		if ( GetFileAttributes(szFilename) != 0xffffffff )
			return true;
		else
			return false;
	}

} 

bool IsDir(const char *szPath)
{
	DWORD dwTemp = GetFileAttributes(szPath);
	if ( dwTemp != 0xffffffff && (dwTemp & FILE_ATTRIBUTE_DIRECTORY) )
		return true;
	else
		return false;

} 

bool DeleteFile(const char *szFilename)
{
	WIN32_FIND_DATA	findData;
	bool			bFound = false;				// Not found initially
	char			szDrive[_MAX_PATH+1];
	char			szDir[_MAX_PATH+1];
	char			szFile[_MAX_PATH+1];
	char			szExt[_MAX_PATH+1];
	char			szTempPath[_MAX_PATH+1];

	// Get full path and remove trailing \s
	GetFullPathName(szFilename, szTempPath);

	// If the source is a directory then add *.* to the end
	if (IsDir(szTempPath))
		strcat(szTempPath, "\\*.*");

	// Split the target into bits (used for reconstruction later)
	_splitpath( szTempPath, szDrive, szDir, szFile, szExt );

	// Delete all files matching the criteria
	HANDLE hSearch = FindFirstFile(szTempPath, &findData);
	bool bLoop = true;
	while (hSearch != INVALID_HANDLE_VALUE && bLoop == true)
	{
		// Make sure the returned handle is a file and not a directory before we
		// try and do delete type things on it!
		if ( (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY)
		{
			bFound = true;							// Found at least one match

			// Returned data is just the filename, we need to add the path stuff back on
			// The find strcut only returns the file NAME, we need to reconstruct the path!
			strcpy(szTempPath, szDrive);
			strcat(szTempPath, szDir);
			strcat(szTempPath, findData.cFileName);

			if ( ::DeleteFile(szTempPath) != TRUE )
			{
				FindClose(hSearch);
				return false;						// Error deleting one of the files
			}

		} // End if

		if (FindNextFile(hSearch, &findData) == FALSE)
			bLoop = false;
	}

	FindClose(hSearch);

	return bFound;

} 

bool CopyFile(const char *szInputSource, const char *szInputDest, bool bOverwrite, bool bMove)
{
	WIN32_FIND_DATA	findData;
	bool			bFound = false;				// Not found initially
	BOOL			bRes;

	char			szSource[_MAX_PATH+1];
	char			szDest[_MAX_PATH+1];
	char			szExpandedDest[MAX_PATH+1];
	char			szTempPath[_MAX_PATH+1];

	char			szDrive[_MAX_PATH+1];
	char			szDir[_MAX_PATH+1];
	char			szFile[_MAX_PATH+1];
	char			szExt[_MAX_PATH+1];

	// Get local version of our source/dest with full path names, strip trailing \s
	GetFullPathName(szInputSource, szSource);
	GetFullPathName(szInputDest, szDest);

	// Check if the files are on different volumes (affects how we do a Move operation)
	bool bDiffVol = IsDifferentVolumes(szSource, szDest);

	// If the source or dest is a directory then add *.* to the end
	if (IsDir(szSource))
		strcat(szSource, "\\*.*");
	if (IsDir(szDest))
		strcat(szDest, "\\*.*");


	// Split source into file and extension (we need this info in the loop below to recontstruct the path)
	_splitpath( szSource, szDrive, szDir, szFile, szExt );

	// Note we now rely on the SOURCE being the contents of szDrive, szDir, szFile, etc.

	// Does the source file exist?
	HANDLE hSearch = FindFirstFile(szSource, &findData);
	bool bLoop = true;
	while (hSearch != INVALID_HANDLE_VALUE && bLoop == true)
	{
		bFound = true;							// Found at least one match

		// Make sure the returned handle is a file and not a directory before we
		// try and do copy type things on it!
		if ( (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY)
		{
			// Expand the destination based on this found file
			ExpandFilenameWildcard(findData.cFileName, szDest, szExpandedDest);

			// The find struct only returns the file NAME, we need to reconstruct the path!
			strcpy(szTempPath, szDrive);
			strcat(szTempPath, szDir);
			strcat(szTempPath, findData.cFileName);

			// Does the destination exist? - delete it first if it does (unless we not overwriting)
			if ( DoesFileExist(szExpandedDest) )
			{
				if (bOverwrite == false)
				{
					FindClose(hSearch);
					return false;					// Destination already exists and we not overwriting
				}
				else
					::DeleteFile(szExpandedDest);
			}

			// Move or copy operation?
			if (bMove == true)
			{
				if (bDiffVol == false)
				{
					bRes = MoveFile(szTempPath, szExpandedDest);
				}
				else
				{
					// Do a copy then delete (simulated copy)
					if ( (bRes = ::CopyFile(szTempPath, szExpandedDest, FALSE)) != FALSE )
						bRes =::DeleteFile(szTempPath);
				}
			}
			else
				bRes = ::CopyFile(szTempPath, szExpandedDest, FALSE);

			if (bRes == FALSE)
			{
				FindClose(hSearch);
				return false;						// Error copying/moving one of the files
			}

		} // End If

		if (FindNextFile(hSearch, &findData) == FALSE)
			bLoop = false;

	} // End while

	FindClose(hSearch);

	return bFound;

} 

void UT()
{
	const char* szFilename = "C:\\abc.txt";
	FILE* pFile = fopen(szFilename, "w");
	fputs ("fopen example",pFile);
	fclose(pFile);
	
	printf("DoesFileExist(%s) : %s\n", szFilename, DoesFileExist(szFilename) ? "true" : "false");

	const char *szPath = "C:\\";
	printf("IsDir(%s) : %s\n", szPath, IsDir(szPath) ? "true" : "false");

	const char *szInputSource = szFilename;
	const char *szInputDest = "C:\\abc.txt.dest";
	bool bOverwrite = true;
	bool bMove = false;
	printf("CopyFile(%s, %s, %s, %s) : %s\n", 
		szInputSource, 
		szInputDest,
		bOverwrite ? "true" : "false",
		bMove ? "true" : "false",
		CopyFile(szInputSource, szInputDest, bOverwrite, bMove) ? "true" : "false");

	printf("bool DeleteFile(%s) : %s\n", szInputSource, DeleteFile(szInputSource) ? "true" : "false");
	printf("bool DeleteFile(%s) : %s\n", szInputDest, DeleteFile(szInputDest) ? "true" : "false");

}

} // namespace pathlib

//////////////////////////////////////////////////////////////////////////

void StripTrailingDir(char *szPath)
{
	int len = (int)strlen(szPath)-1;

	if (szPath[len] == '\\')
		szPath[len] = '\0';

} 

void GetFullPathName(const char *szIn, char *szOut)
{
	char	*szFilePart;

	GetFullPathName(szIn, _MAX_PATH, szOut, &szFilePart);
	StripTrailingDir(szOut);

} 

bool IsDifferentVolumes(const char *szPath1, const char *szPath2)
{
	char			szP1Drive[_MAX_DRIVE+1];
	char			szP2Drive[_MAX_DRIVE+1];

	char			szP1Dir[_MAX_DIR+1];
	char			szP2Dir[_MAX_DIR+1];

	char			szFile[_MAX_FNAME+1];
	char			szExt[_MAX_EXT+1];

	char			szP1[_MAX_PATH+1];
	char			szP2[_MAX_PATH+1];

	// Get full pathnames
	GetFullPathName(szPath1, szP1);
	GetFullPathName(szPath2, szP2);

	// Split the target into bits
	_splitpath( szP1, szP1Drive, szP1Dir, szFile, szExt );
	//	WinPrintf("", "%s - %s - %s - %s", szP1Drive, szP1Dir, szFile, szExt);
	_splitpath( szP2, szP2Drive, szP2Dir, szFile, szExt );
	//	WinPrintf("", "%s - %s - %s - %s", szP2Drive, szP2Dir, szFile, szExt);

	if (szP1Drive[0] == '\0' && szP2Drive[0] == '\0' )
	{
		// Both paths are UNC, if both directories are also the same then we assume
		// they are on the same volume (same UNC but different directories may be redirected
		// so I don't think we can assume they are the same volume in that case.
		if ( !stricmp(szP1Dir, szP2Dir) )
			return false;
		else
			return true;
	}
	else
	{
		if ( !stricmp(szP1Drive, szP2Drive) )
			return false;
		else
			return true;
	}

} // IsDifferentVolumes()

void ExpandFilenameWildcard(const char *szSource, const char *szDest, char *szExpandedDest)
{
	// copy one.two.three  *.txt     = one.two   .txt
	// copy one.two.three  *.*.txt   = one.two   .three  .txt
	// copy one.two.three  *.*.*.txt = one.two   .three  ..txt
	// copy one.two		   test      = test

	char	szFileTemp[_MAX_PATH+1];
	char	szExtTemp[_MAX_PATH+1];

	char	szSrcFile[_MAX_PATH+1];
	char	szSrcExt[_MAX_PATH+1];

	char	szDestDrive[_MAX_PATH+1];
	char	szDestDir[_MAX_PATH+1];
	char	szDestFile[_MAX_PATH+1];
	char	szDestExt[_MAX_PATH+1];

	// If the destination doesn't include a wildcard, send it back vertabim
	if (strchr(szDest, '*') == NULL)
	{
		strcpy(szExpandedDest, szDest);
		return;
	}

	// Split source and dest into file and extension
	_splitpath( szSource, szDestDrive, szDestDir, szSrcFile, szSrcExt );
	_splitpath( szDest, szDestDrive, szDestDir, szDestFile, szDestExt );

	// Source and Dest ext will either be ".nnnn" or "" or ".*", remove the period
	if (szSrcExt[0] == '.')
		strcpy(szSrcExt, &szSrcExt[1]);
	if (szDestExt[0] == '.')
		strcpy(szDestExt, &szDestExt[1]);

	// Start of the destination with the drive and dir
	strcpy(szExpandedDest, szDestDrive);
	strcat(szExpandedDest, szDestDir);

	// Replace first * in the destext with the srcext, remove any other *
	ExpandFilenameWildcardPart(szSrcExt, szDestExt, szExtTemp);

	// Replace first * in the destfile with the srcfile, remove any other *
	ExpandFilenameWildcardPart(szSrcFile, szDestFile, szFileTemp);

	// Concat the filename and extension if req
	if (szExtTemp[0] != '\0')
	{
		strcat(szFileTemp, ".");
		strcat(szFileTemp, szExtTemp);
	}
	else
	{
		// Dest extension was blank SOURCE MIGHT NOT HAVE BEEN!
		if (szSrcExt[0] != '\0')
		{
			strcat(szFileTemp, ".");
			strcat(szFileTemp, szSrcExt);
		}
	}

	// Now add the drive and directory bit back onto the dest
	strcat(szExpandedDest, szFileTemp);

} // CopyFile

void ExpandFilenameWildcardPart(const char *szSource, const char *szDest, char *szExpandedDest)
{
	int		i = 0, j = 0, k = 0;

	// Replace first * in the dest with the src, remove any other *
	char *lpTemp = strchr((char*)szDest, '*');
	if (lpTemp != NULL)
	{
		// Contains at least one *, copy up to this point
		while(szDest[i] != '*')
			szExpandedDest[j++] = szDest[i++];
		// Skip the * and replace in the dest with the srcext
		while(szSource[k] != '\0')
			szExpandedDest[j++] = szSource[k++];
		// Skip any other *
		i++;
		while(szDest[i] != '\0')
		{
			if (szDest[i] == '*')
				i++;
			else
				szExpandedDest[j++] = szDest[i++];
		}
		szExpandedDest[j] = '\0';
	}
	else
	{
		// No wildcard, straight copy of destext
		strcpy(szExpandedDest, szDest);
	}

} // ExpandFilenameWildcardPart()